#include<sys/types.h>  
#include<sys/socket.h>  
#include<netinet/in.h>  
#include<arpa/inet.h>  
#include<errno.h>  
#include<unistd.h>  

/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
  
#include<stdio.h>  
#include<string.h>  
#include<stdlib.h>  
#include <sys/sysinfo.h>
  
#include <json/json.h>

#include <string>
#include <boost/program_options.hpp> 
namespace po = boost::program_options; 

#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>  
#include <boost/lexical_cast.hpp>
using namespace boost::asio; 

#include <boost/thread/thread.hpp>  
using namespace boost;

#include <boost/format.hpp>

#include "socket_widget.h"

#define DEFAULT_THREADS 1
#define DEFAULT_POOL_PORT 3333
#define DEFAULT_POOL_ADDR "pool.electroneum.space"
#define DEFAULT_WALLET_ADDR "etnkLwPGNHxZbn3fEg4nwqNQYWFAZ8ZQ48z1tofDNgzN7a9UEGHA4vfQDBFYYhkynvctjDeYs7wfDbyyoKauAYxs9CCC2EUaN5"
#define DEFAULT_WALLET_PASS "x"

using namespace std;

typedef struct miner_main_st {
    int run;
    ip_address_t srv_addr;
    short srv_port;
    string srv_domain;
    string srv_ip;
    string wallet_addr;
    string wallet_pass;
    int nthreads;
    vector<boost::thread *> threads;
} miner_main_t;

static miner_main_t miner_main;

int domain2ip(const char *domain, vector<string> &ips)
{
    io_service ios;
    ip::tcp::resolver slv(ios);
    ip::tcp::resolver::query qry(domain, boost::lexical_cast<string>(0));
    ip::tcp::resolver::iterator it=slv.resolve(qry);  
    ip::tcp::resolver::iterator end;
    for(; it != end; it++) {  
        ips.push_back((*it).endpoint().address().to_string());  
    }  
    return ips.size();
}

int cmdline_parser(int argc, char **argv)
{
    string ipaddr;
    short port;
    int ncpus = get_nprocs();

    po::options_description desc("Allowed options"); 
    desc.add_options()  
        ("help", "print help message")  
        ("server", po::value<string>(&ipaddr)->default_value(DEFAULT_POOL_ADDR), "set address of server")
        ("port", po::value<short>(&port)->default_value(DEFAULT_POOL_PORT), "set port of server")
        ("threads", po::value<int>(&miner_main.nthreads)->default_value(DEFAULT_THREADS), "set num of threads")
        ("wallet_addr", po::value<string>(&miner_main.wallet_addr)->default_value(DEFAULT_WALLET_ADDR), "set address of server")
        ("wallet_pass", po::value<string>(&miner_main.wallet_pass)->default_value(DEFAULT_WALLET_PASS), "set address of server");

    po::variables_map vm;  
    po::store(po::parse_command_line(argc, argv, desc), vm);  
    po::notify(vm);  
  
    if (vm.count("help")) {  
        cout << desc << endl;  
        return -1;  
    }  

    if (vm.count("server")) {
        int ret;
        vector<string> ips;
        struct sockaddr_in saddr = {0};

        miner_main.srv_domain = ipaddr;
        ret = domain2ip(ipaddr.c_str(), ips);
        if (ret <= 0) {
            printf("cannot resolve %s\n", ipaddr.c_str());
            return -1;
        }
        miner_main.srv_ip = ips[0];
        inet_aton(ips[0].c_str(), &saddr.sin_addr);
        miner_main.srv_addr.ip = saddr.sin_addr.s_addr;
        miner_main.srv_addr.family = AF_INET;
    }
    
    if (vm.count("port")) {
        miner_main.srv_port = htons(port);
    }

    if (miner_main.nthreads > ncpus) {
        miner_main.nthreads = ncpus;
    }
  
    printf("address     : %s\n", miner_main.srv_domain.c_str());
    printf("ip          : %s\n", miner_main.srv_ip.c_str());
    printf("port        : %d\n", port);
    printf("threads     : %d\n", miner_main.nthreads);
    printf("wallet_addr : %s\n", miner_main.wallet_addr.c_str());
    printf("wallet_pass : %s\n", miner_main.wallet_pass.c_str());
    return 0;
}

socket_t stratum_connect(const ip_address_t *addr, int port)
{
    int ret;
    ip_address_t cli_addr;
    cli_addr.ip = INADDR_ANY;
    cli_addr.family = AF_INET;

    socket_t sk = socket_tcp_client(&cli_addr, 0);
    assert(sk >= 0);
    ret = socket_connect_n(sk, addr, port, 5);
    if (ret < 0) {
        printf("connect to stratum server failed!\n");
        return -1;
    }
    return sk;
}

int stratum_login(socket_t sk, int id)
{
    miner_main_t *mm = &miner_main;
    Json::Value root;
    Json::FastWriter writer;
    Json::Value params;
    string jsstr;
    
    root["id"] = id;
    root["method"] = "login";
    root["params"]["login"] = mm->wallet_addr;
    root["params"]["pass"] = mm->wallet_pass;
    root["params"]["agent"] = boost::str(boost::format("etnminer:%d") % id);

    jsstr = writer.write(root);
    printf("%s\n", jsstr.c_str());
    socket_write(sk, jsstr.c_str(), jsstr.length());
    return 0;
}

void stratum_disconnect(int sk)
{
    socket_close(sk);
}

void stratum_dispatch(int sk, char *rcv_buf, ssize_t len)
{
    int ret;
    Json::Value root;
    Json::Reader reader;

    printf("%s\n", rcv_buf);

    ret = reader.parse(rcv_buf, root);
    if (ret < -1) {
        printf("json parse failed!\n");
        return ;
    }
    if (!root["result"]["job"].isNull()) {
        //printf("%s\n", root["result"]["job"].asString().c_str());
        printf("%s\n", root["result"]["job"]["blob"].asString().c_str());
    }

    if (root["method"] == "job") {
        printf("%s\n", root["params"]["blob"].asString().c_str());
    }
}

struct miner_thread {
    miner_thread(int id):id(id+1) {
        sk = -1;
    }
    
    void operator()() {
        miner_main_t *mm = &miner_main;
        sk = stratum_connect(&mm->srv_addr, mm->srv_port);
        if (sk < 0) {
            return ;
        }
        stratum_login(sk, id);
        while (mm->run) {
            int ret;
            struct timeval tv;
            fd_set readfs;
            FD_ZERO(&readfs);
            FD_SET(sk, &readfs);

            tv.tv_sec = 0;
            tv.tv_usec = 1000000;
            ret = select(sk + 1, &readfs, NULL, NULL, &tv);
            if (ret > 0) {
                if (FD_ISSET(sk, &readfs)) {
                    ssize_t len = socket_read(sk, rcv_buf, sizeof(rcv_buf));
                    if (len > 0) {
                        stratum_dispatch(sk, rcv_buf, len);
                    }
                }
            }
        }
        stratum_disconnect(sk);
    }
    int id;
    socket_t sk;
    char rcv_buf[1024];
};

void start_threads(void)
{
    int i;
    miner_main.run = 1;
    for (i = 0; i < miner_main.nthreads; ++i) {
        boost::thread *th = new boost::thread(miner_thread(i));
        miner_main.threads.push_back(th);
    }

    for (i = 0; i < miner_main.nthreads; ++i) {
        boost::thread *th = miner_main.threads[i];
        th->join();
    }
}

int main(int argc, char **argv)
{
    cmdline_parser(argc, argv);
    start_threads();
    return 0;
}
